﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace JobDemo02
{
    class ImagingContextOptions
    {
        public List<int> Indices { get; init; }

        public bool HvOff { get; init; } = true;
    }

    class ImagingContext(JobContext ctx, ImagingContextOptions options)
    {
        readonly Channel<ImagingResult> channel = Channel.CreateUnbounded<ImagingResult>();

        public JobContext JobContext => ctx;

        public IAsyncEnumerable<ImagingResult> Source => channel.Reader.ReadAllAsync();

        public bool HvOff => options.HvOff;

        public Task CheckPoint()
        {
            return ctx.CheckPoint();
        }

        public void Write(ImagingResult img)
        {
            channel.Writer.TryWrite(img);
        }

        public Task<AlignmentHint> AwaitManualAssist()
        {
            return ctx.AwaitManualAssist();
        }

        public async ValueTask Complete(Exception ex = null)
        {
            channel.Writer.TryComplete(ex);
        }
    }


    class JobContext()
    {
        readonly Channel<object> channel = Channel.CreateBounded<object>(1);

        int closed;

        int state = 0b10;
        int waitingForUser;

        public Guid Id { get; } = Guid.NewGuid();

        public Task Completion => channel.Reader.Completion;

        // lifetime managed by job queue
        public MessageQueue<PatternResult> MessageQueue { get; } = new();

        public void Block(Task task)
        {
            if ((Interlocked.CompareExchange(ref state, 0, 0) & 1) == 1)
            {
                throw new InvalidOperationException("Processing has completed.");
            }

            if (!channel.Writer.TryWrite(task))
            {
                throw new InvalidOperationException("Block was not possible!");
            }
        }

        public void ManualAssist(AlignmentHint hint)
        {
            if (Interlocked.CompareExchange(ref waitingForUser, 0, 1) != 1)
            {
                throw new InvalidOperationException("Unexpected ManualAssist");
            }

            if (!channel.Writer.TryWrite(hint))
            {
                throw new InvalidOperationException("ManualAssist was not possible!");
            }
        }

        public void RequestCancellation()
        {
            Interlocked.Or(ref state, 0b01);
        }

        public void Confirm()
        {
            var s = Interlocked.CompareExchange(ref state, 0b01, 0);
            if ((s & 0b10) != 0)
            {
                throw new InvalidOperationException("Processing not finished.");
            }

            Close(null);
        }

        public Task CheckPoint()
        {
            if ((Interlocked.CompareExchange(ref state, 0, 0) & 1) == 1)
            {
                throw new OperationCanceledException();
            }

            channel.Reader.TryRead(out var task);
            return (Task)task;
        }

        public async Task<AlignmentHint> AwaitManualAssist()
        {
            Interlocked.Exchange(ref waitingForUser, 1);
            return (AlignmentHint)await channel.Reader.ReadAsync().ConfigureAwait(false);
        }

        public void Reset()
        {
            var s = Interlocked.CompareExchange(ref state, 0b10, 0);
            if (s != 0)
            {
                throw new InvalidOperationException("Can not reset.");
            }
        }

        public void OnProcessingFinished()
        {
            Interlocked.And(ref state, 0b01);
        }

        public void Complete(Exception ex = null)
        {
            Close(ex);
        }

        void Close(Exception ex)
        {
            if (Interlocked.CompareExchange(ref closed, 1, 0) == 0)
            {
                channel.Writer.TryComplete(ex);
            }
        }
    }
}